package net.avcompris.binding.sax.impl;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;
import static net.avcompris.binding.sax.CastUtils.incArray;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import com.avcompris.common.annotation.Nullable;
import com.avcompris.lang.NotImplementedException;

final class ValuesHolder implements InvocationHandler {

	@Override
	public String toString() {
		
		return "[" + getClass().getSimpleName()+": "+elementUnmarshall+"]";
	}
	
	public ValuesHolder(
			final ClassLoader classLoader,
			final ElementUnmarshall elementUnmarshall) {

		nonNullArgument(classLoader, "classLoader");

		this.elementUnmarshall = nonNullArgument(elementUnmarshall,
				"elementUnmarshall");

		proxy = Proxy.newProxyInstance(classLoader, new Class<?>[]{
			elementUnmarshall.getClassToInstantiate()
		}, this);

		// Initialize arrays.

		for (final Method method : elementUnmarshall.getAllMethods()) {

			final Class<?> returnType = method.getReturnType();

			if (returnType.isArray()) {

				values.put(method.getName(), Array.newInstance(returnType
						.getComponentType(), 0));
			}
		}
	}

	private final ElementUnmarshall elementUnmarshall;

	public ElementUnmarshall getElementUnmarshall() {

		return elementUnmarshall;
	}

	private final Object proxy;

	public Object getObjectProxy() {

		return proxy;
	}

	public boolean hasValue(final Method method) {

		nonNullArgument(method, "method");

		final String methodName = method.getName();

		return values.containsKey(methodName);
	}

	public void setValue(final Method method, final Object value) {

		nonNullArgument(method, "method");
		nonNullArgument(value, "value");

		final String methodName = method.getName();

		values.put(methodName, value);
	}

	public void addValueToArray(final Method method, final Object value) {

		nonNullArgument(method, "method");
		nonNullArgument(value, "value");

		final String methodName = method.getName();

		final Object array = incArray(
				method.getReturnType().getComponentType(), values
						.get(methodName), value);

		values.put(methodName, array);
	}

	private final Map<String, Object> values = new HashMap<String, Object>();

	@Override
	public Object invoke(final Object proxy, final Method method,
			@Nullable final Object[] args) throws Throwable {

		nonNullArgument(proxy, "proxy");
		nonNullArgument(method, "method");

		if (args != null && args.length != 0) {
			throw new NotImplementedException("Method has parameters ("
					+ args.length + "): " + method);
		}

		final Object value = values.get(method.getName());

		final Class<?> returnType = method.getReturnType();

		if (value == null && String.class.equals(returnType)) {

			return ""; // default value for a non-existing value in DOM: empty
		}

		return value;
	}
}
